package net.borderwars.webstartclient;

import net.borderwars.beans.Player;
import net.borderwars.jedit.JEditTextArea;
import net.borderwars.jedit.JavaTokenMarker;
import net.borderwars.userserver.*;

import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.Socket;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.Semaphore;
import java.util.logging.Logger;

/**
 * @author ehubbard
 *         Date: Aug 10, 2005
 *         Time: 11:05:05 AM
 */
public class Client implements MethodCallProvider {

    JEditTextArea ta = new JEditTextArea ();

    private static Logger log = Logger.getLogger ("Client");

    private DataOutputStream dos;
    private DataInputStream dis;

    private ObjectSender sender;
    private ObjectReceiver receiver;

    private HashMap<Long, Semaphore> waiters = new HashMap<Long, Semaphore> ();
    HashMap<Long, InvocationResult> results = new HashMap<Long, InvocationResult> ();
    private ServerInterface server;

    private Object currentTreeSelection;

    private Player ourPlayer;
    private String userName;
    private String password;

    public static void main (String[] args) throws ClassNotFoundException, SQLException, IOException, InterruptedException {
        if (args.length < 2) {
            System.err.println ("Please pass us the username and password on the commandline ");
            System.exit (1);
        }
        Client c = new Client (args[0].trim (), args[1].trim ());
    }

    private static byte[] getBytes (Serializable s) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream ();
        try {
            ObjectOutputStream oos = new ObjectOutputStream (baos);
            oos.writeObject (s);
            oos.flush ();
            return (baos.toByteArray ());
        }
        catch (IOException e) {
            e.printStackTrace ();
        }
        return (null);
    }

    public Client (final String userName, final String password) throws ClassNotFoundException, SQLException, IOException, InterruptedException {
        this.userName = userName;
        this.password = password;
        ta.setTokenMarker (new JavaTokenMarker ());

        createLocalStreams ();
        sender = new ObjectSender (dos);
        receiver = new ObjectReceiver (dis);
        server = getServer (userName, password);

        ourPlayer = server.getPlayer ();
        System.out.println ("Our player is " + ourPlayer);
        System.out.println ("Public bots: " + server.getPublicRobots ());
        System.out.println ("He Wrote " + ourPlayer.getWrote ());
        ClientFrame cf = new ClientFrame (server);

    }

    private void createLocalStreams () throws IOException {
        InetAddress lh = InetAddress.getLocalHost ();
        Socket s = new Socket (lh, 8989);
        dos = new DataOutputStream (s.getOutputStream ());
        dis = new DataInputStream (s.getInputStream ());
    }

    public ServerInterface getServer (final String userName, final String password) throws ClassNotFoundException, IOException {
        Object args[] = new Object[2];
        args[0] = userName;
        args[1] = password;

        Class types[] = new Class[2];
        types[0] = userName.getClass ();
        types[1] = password.getClass ();

        Invocation i = new Invocation ("login", types, args, -1);
        InvocationResult ir = makeMethodCall (i);
        Object o = ir.getResult ();
        if (o instanceof ObjectRef) {
            ObjectRef objectRef = (ObjectRef) o;
            objectRef.setClient (this);
            o = objectRef.makeProxy ();
        }
        if (o instanceof InvocationTargetException) {
            InvocationTargetException throwable = (InvocationTargetException) o;
            throw new UnsupportedOperationException (throwable.getCause ());
        }

        return ((ServerInterface) o);
    }

    public InvocationResult makeMethodCall (final Invocation i) throws IOException {
        Semaphore s = new Semaphore (0);
        waiters.put (i.getId (), s);
        sender.sendObject (i);
        try {
            s.acquire ();
            InvocationResult ir = results.remove (i.getId ());
            waiters.remove (i.getId ());
            return (ir);
        }
        catch (InterruptedException e) {
            e.printStackTrace ();
        }
        throw new IOException ("method call failed!");
    }

    private class ObjectReceiver extends Thread {
        private DataInputStream dis;

        public ObjectReceiver (DataInputStream dis) {
            this.dis = dis;
            super.setDaemon (true);
            super.setName ("Object Receiver");
            start ();
        }

        public void run () {
            while (true) {
                try {
                    int size = dis.readInt ();
                    byte data[] = new byte[size];
                    dis.read (data);
                    Serializable obj = getObject (data);
                    if (obj instanceof InvocationResult) {
                        InvocationResult ir = (InvocationResult) obj;
                        results.put (ir.getId (), ir);
                        Semaphore lock = waiters.get (ir.getId ());
                        lock.release ();
                    }
                    else
                        if (obj instanceof Invocation) {
                            Invocation i = (Invocation) obj;
                            Method m = Client.class.getMethod (i.getMethodName (), i.getTypes ());
                            Object result = m.invoke (this, i.getArgs ());
                            InvocationResult ir = new InvocationResult (result, i.getId ());
                            sender.sendObject (ir);
                        }

                }
                catch (Exception e) {
                    e.printStackTrace ();
                    System.exit (1);
                }

            }
        }

        private Serializable getObject (final byte[] data) throws IOException, ClassNotFoundException {
            ByteArrayInputStream bais = new ByteArrayInputStream (data);
            ObjectInputStream ois = new ObjectInputStream (bais);
            return (Serializable) ois.readObject ();
        }

    }

    private class ObjectSender extends Thread {
        private DataOutputStream dos;
        private PriorityBlockingQueue<Serializable> queue = new PriorityBlockingQueue<Serializable> ();

        public ObjectSender (DataOutputStream dos) {
            this.dos = dos;
            super.setDaemon (true);
            super.setName ("Object Sender");
            start ();

        }

        public void send (Serializable s) {
            queue.offer (s);
        }

        public void run () {
            while (true) {
                Serializable o = null;
                try {
                    o = queue.take ();
                }
                catch (InterruptedException e) {
                    e.printStackTrace ();  //To change body of catch statement use File | Settings | File Templates.
                }
                try {
                    sendObject (o);
                }
                catch (IOException e) {
                    e.printStackTrace ();  //To change body of catch statement use File | Settings | File Templates.
                }
            }
        }

        private void sendObject (Serializable s) throws IOException {
            byte data[] = getBytes (s);
            dos.writeInt (data.length);
            dos.write (data);
            dos.flush ();
        }

    }

}
